using System;
using System.Buffers;
using System.Threading.Tasks;
using eLibrary;
using ePipe;
using eTask.Extensions;
using Letter.IO;
using Kcptun =  Letter.Net.Kcp.Library.KCP.KCP;

namespace Letter.Net.Kcp;

partial class KcpSession
{
    sealed class KcpSessionSnder : IAsyncReleasable
    {
        public KcpSessionSnder(KcpSession transport, KcpMode mode, Func<IPipe<byte>> func)
        {
            this.transport = transport;
            this.inputChannel = func.Invoke();
            this.outputChannel = Pipeline.CreateMultiplePipe<byte>(PipeOptions<byte>.Default);
        }
        
        private IPipe<byte> inputChannel;
        private IPipe<byte> outputChannel;
        private KcpSession transport;
        private bool WriteDelay = false;

        private Task readInputTask;
        private Task readOutputTask;

        private Kcptun Kcp
        {
            get { return this.transport.kcp; }
        }

        private Func<int, bool> TryCheckLength 
        {
            get { return transport.checkLengthFunc; }
        }

        private IPipeReader<byte> InputReader
        {
            get { return this.inputChannel.Reader; }
        }

        public IPipeWriter<byte> InputWriter
        {
            get { return this.inputChannel.Writer; }
        }

        private IPipeReader<byte> OutputReader
        {
            get { return this.outputChannel.Reader; }
        }

        private IPipeWriter<byte> OutputWriter
        {
            get { return this.outputChannel.Writer; }
        }

        public void Start()
        {
            this.readInputTask = this.ReadInputAsync();
            this.readOutputTask = this.ReadOuterAsync();
        }

        private async Task ReadInputAsync()
        {
            while (true)
            {
                var result = await this.InputReader.ReadAsync();
                if (result.IsCanceled || result.IsCompleted)
                {
                    break;
                }

                var buf = result.Buffer;
                var length = (int)buf.Length;
                if (!this.TryCheckLength(length))
                {
                    break;
                }

                if (length > 0)
                {
                    foreach (var item in buf)
                    {
                        if (item.Length > 0)
                        {
                            var segment = item.GetArray();
                            while (true)
                            {
                                if (this.Send(segment.Array, segment.Offset, segment.Count) > 0)
                                {
                                    break;
                                }
                            }
                        }
                        this.InputReader.AdvanceTo(buf.End);
                        this.InputReader.Flush();
                    }
                }
            }
            
            this.InputReader.Complete();
        }
        
        private int Send(byte[] data, int index, int length)
        {
            var waitsnd = this.Kcp.WaitSnd;
            if (waitsnd < this.Kcp.SndWnd && waitsnd < this.Kcp.RmtWnd)
            {
                var sendBytes = 0;
                do
                {
                    var n = Math.Min((int)this.Kcp.Mss, length - sendBytes);
                    this.Kcp.Send(data, index + sendBytes, n);
                    sendBytes += n;
                } 
                while (sendBytes < length);

                waitsnd = this.Kcp.WaitSnd;
                if (waitsnd >= this.Kcp.SndWnd || waitsnd >= this.Kcp.RmtWnd || !WriteDelay)
                {
                    this.Kcp.Flush(false);
                }

                return length;
            }

            return 0;
        }

        public void OnKcpRawSend(byte[] buffer, int length)
        {
            this.OutputWriter.Write(buffer.AsMemory(0, length).Span);
            this.OutputWriter.FlushAsync().Ignore();
        }

        private async Task ReadOuterAsync()
        {
            while (true)
            {
                var result = await this.OutputReader.ReadAsync();
                if (result.IsCanceled || result.IsCompleted)
                {
                    break;
                }

                var buf = result.Buffer;
                this.transport.WriteUdpMessage(MessageType.K_MSG, ref buf);
                this.OutputReader.AdvanceTo(buf.End);
                this.OutputReader.Flush();
            }
        }

        public async ValueTask ReleaseAsync()
        {
            this.InputReader.Complete();
            this.OutputReader.Complete();
            
            await this.readInputTask;
            await this.readOutputTask;
        }
    }
}